﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ors.RInterface.Engine.PInvoke;
using System.Runtime.InteropServices;

namespace Ors.RInterface.Engine
{
    public class REngine:IDisposable
    {
        private string homeDirectory;
        private string homeUser;
        RStart RStart;
        static REngine instance;
        GCHandle[] keepHandles = new GCHandle[10];


        internal string[] GetClass(IntPtr obj)
        {
            IntPtr @class = R.Rf_getAttrib(obj,R_ClassSymbol);
            R.Rf_protect(@class);
            int len = R.Rf_length(@class);
            string[] res = new string[len];
            for (int i = 0; i < res.Length; ++i)
            {
                res[i] = R.Rf_translateChar(R.STRING_ELT(@class, i));
            }
            return res;
        }

        bool interactive;
        /// <summary>
        /// Fires when the engine needs to write something to the user
        /// </summary>
        public event EventHandler<MessageEventArgs> WriteMessage;
        /// <summary>
        /// Fires when, in interactive mode, the engine is ready to receive
        /// an input string.
        /// </summary>
        public event EventHandler<AskUserEventArgs> AskForInput;
        /// <summary>
        /// Creates a non-interactive engine
        /// Please nothe that a single instance per-process is allowed.
        /// </summary>
        /// <returns>The engine instance</returns>
        static public REngine Create()
        {
            return Create(false);
        }
        /// <summary>
        /// Creates or jon an engine instance
        /// </summary>
        /// <param name="bInteractive">true if the instance has to be interactive</param>
        /// <returns></returns>
        public static REngine GetInstance(bool bInteractive)
        {
            if (null == instance)
                return Create(bInteractive);
            else
            {
                if (bInteractive != instance.interactive)
                    throw new Exception("Can't merge two different interactive mode in the same process.");
                return instance;
            }
        }
        /// <summary>
        /// Creates an engine in interactive/non-interactive mode
        /// </summary>
        /// <param name="interactive">if the engine has to be interactive</param>
        /// <returns>The engine instance</returns>
        static public REngine Create(bool interactive)
        {
            lock (typeof(REngine))
            {
                if (instance == null)
                {
                    instance = new REngine(interactive);
                }
                else
                {
                    throw new Exception("Since R dll is mono-threaded only one engine can be created");
                }
                return instance;
            }
        }
        /// <summary>
        /// Get an entry point to obtain a variable value.
        /// </summary>
        /// <param name="name">The symbol name</param>
        /// <returns>An accessor to the variable value</returns>
        public IVarAccessor GetVar(string name)
        {
            return new VarAccessor(name, R_Global_Env,this);
        }
        /// <summary>
        /// Assign a matrix to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="matrix">a double array representing the matrix</param>
        /// <returns></returns>
        public REngine SetVar(string name, double[][] matrix)
        {
            int lLen = GetJaggerSize(matrix);
            IntPtr val = R.Rf_allocVector(SEXPTYPE.REALSXP,lLen);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);
            int rowLen = -1;
            IntPtr array = R.REAL(val);
            unsafe
            {
                double* dPtr = (double*)array.ToPointer();
                for (int i = 0; i < matrix.Length; ++i)
                {
                    if (i == 0)
                        rowLen = matrix[i].Length;
                    else
                        if (matrix[i].Length != rowLen)
                        {
                            R.Rf_unprotect(2);
                            throw new Exception("Each row of a matrix must be the same length");
                        }
                    Marshal.Copy(matrix[i], 0, (IntPtr)dPtr, matrix[i].Length);
                    dPtr += matrix[i].Length;
                }
            }
            R.Rf_setVar(pname, val, R_Global_Env);

            
            IntPtr dim = R.Rf_install("dim");
            R.Rf_protect(dim);
            IntPtr dims = GetProtectedVectorForType(typeof(int),2);
            FillVector(dims,new object[]{rowLen,matrix.Length},typeof(int));
            R.Rf_setAttrib(val, dim,dims );
            R.Rf_unprotect(2);

            R.Rf_unprotect(2);
            return this;
        }

        /// <summary>
        /// Assign a matrix to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="matrix">an integer array representing the matrix</param>
        /// <returns></returns>
        public REngine SetVar(string name, int[][] matrix)
        {
            int lLen = GetJaggerSize(matrix);
            IntPtr val = R.Rf_allocVector(SEXPTYPE.INTSXP, lLen);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);
            int rowLen = -1;
            IntPtr array = R.INTEGER(val);
            unsafe
            {
                int* dPtr = (int*)array.ToPointer();
                for (int i = 0; i < matrix.Length; ++i)
                {
                    if (i == 0)
                        rowLen = matrix[i].Length;
                    else
                        if (matrix[i].Length != rowLen)
                        {
                            R.Rf_unprotect(2);
                            throw new Exception("Each row of a matrix must be the same length");
                        }
                    Marshal.Copy(matrix[i], 0, (IntPtr)dPtr, matrix[i].Length);
                    dPtr += matrix[i].Length;
                }
            }
            R.Rf_setVar(pname, val, R_Global_Env);


            IntPtr dim = R.Rf_install("dim");
            R.Rf_protect(dim);
            IntPtr dims = GetProtectedVectorForType(typeof(int), 2);
            FillVector(dims, new object[] { rowLen, matrix.Length }, typeof(int));
            R.Rf_setAttrib(val, dim, dims);
            R.Rf_unprotect(2);

            R.Rf_unprotect(2);
            return this;
        }

        private int GetJaggerSize<T>(T[][] matrix)
        {
            int size = 0;
            foreach (var m in matrix)
                size += m.Length;
            return size;
        }

        internal int[] GetMatrixDimensions(IntPtr var)
        {
            IntPtr dims = R.Rf_install("dim");
            R.Rf_protect(dims);
            IntPtr dimPtr = R.Rf_getAttrib(var, dims);
            R.Rf_protect(dimPtr);
            if (!R.Rf_isInteger(dimPtr))
            {
                R.Rf_unprotect(2);
                return new int[0];
            }
            int[] res = new int[R.Rf_length(dimPtr)];
            Marshal.Copy(R.INTEGER(dimPtr), res,0,res.Length);
            R.Rf_unprotect(2);
            return res;
        }
        /*
        internal bool IfMatrix(IntPtr var)
        {
            string[] cls = GetClass(var);
            return cls.Any(q => q == "matrix");
        }*/

        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="value">The value</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name,string value)
        {
            IntPtr val = R.Rf_mkString(value);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);
            R.Rf_setVar(pname,val,R_Global_Env);
            R.Rf_unprotect(2);
            return this;
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="value">The value</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, double value)
        {
            IntPtr val = R.Rf_ScalarReal(value);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);
            R.Rf_setVar(pname, val, R_Global_Env);
            R.Rf_unprotect(2);
            return this;
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="value">The value</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, DateTime dt)
        {
            return SetVar(name,dt,false);
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="value">The value</param>
        /// <param name="forceconvert">Assumes the date in local time, and convert it to GMT</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, DateTime dt,bool forceConvert)
        {
            IntPtr val = R.Rf_allocVector(SEXPTYPE.REALSXP, 1);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);

            IntPtr array = R.REAL(val);
            double[] dates = new double[1];
            if( forceConvert )
                dates[0] = (dt.ToUniversalTime()-new DateTime(1970,1,1,0,0,0)).TotalSeconds;
            else
                dates[0] = (dt - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
            Marshal.Copy(dates, 0, array, 1);

            R.Rf_setVar(pname, val, R_Global_Env);
            //change class to time
            IntPtr cl = GetProtectedVectorForType(typeof(string), 2);
            FillVector(cl, new string[] {"POSIXt","POSIXct" }, typeof(string));
            R.Rf_setAttrib(val, R_ClassSymbol, cl);

            IntPtr tzone = R.Rf_install("tzone");
            R.Rf_protect(tzone);
            R.Rf_setAttrib(val, tzone, R.Rf_mkString("GMT"));

            R.Rf_unprotect(4);
            return this;
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="values">The value</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, double[] values)
        {
            IntPtr val = R.Rf_allocVector(SEXPTYPE.REALSXP,values.Length);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);

            IntPtr array = R.REAL(val);
            Marshal.Copy(values, 0, array, values.Length);
            
            R.Rf_setVar(pname, val, R_Global_Env);

            

            R.Rf_unprotect(2);
            return this;
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="values">The value</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, DateTime[] values)
        {
            return SetVar(name, values, false);
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="values">The value</param>
        /// <param name="convert">Assumes the date in local time, and convert it to GMT</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, DateTime[] values,bool convert)
        {
            IntPtr val = R.Rf_allocVector(SEXPTYPE.REALSXP, values.Length);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);

            IntPtr array = R.REAL(val);
            Marshal.Copy(ToUnix(values,convert), 0, array, values.Length);

            //change class to time
            IntPtr cl = GetProtectedVectorForType(typeof(string), 2);
            FillVector(cl, new string[] { "POSIXt", "POSIXct" }, typeof(string));
            R.Rf_setAttrib(val, R_ClassSymbol, cl);

            IntPtr tzone = R.Rf_install("tzone");
            R.Rf_protect(tzone);
            R.Rf_setAttrib(val, tzone, R.Rf_mkString("GMT"));

            R.Rf_setVar(pname, val, R_Global_Env);
            R.Rf_unprotect(4);
            return this;
        }

        private double[] ToUnix(DateTime[] values,bool convert)
        {
            if( convert )
                return values.Select(dt => (dt.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).ToArray();
            else
                return values.Select(dt => (dt - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).ToArray();
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="values">The value</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, int[] values)
        {
            IntPtr val = R.Rf_allocVector(SEXPTYPE.INTSXP, values.Length);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);

            IntPtr array = R.INTEGER(val);
            Marshal.Copy(values, 0, array, values.Length);

            R.Rf_setVar(pname, val, R_Global_Env);
            R.Rf_unprotect(2);
            return this;
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="values">The value</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, bool[] values)
        {
            IntPtr val = R.Rf_allocVector(SEXPTYPE.LGLSXP, values.Length);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);

            IntPtr array = R.INTEGER(val);
            Marshal.Copy(values.Select(q=>q?1:0).ToArray(), 0, array, values.Length);

            R.Rf_setVar(pname, val, R_Global_Env);
            R.Rf_unprotect(2);
            return this;
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="values">The value</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, string[] values)
        {
            IntPtr val = R.Rf_allocVector(SEXPTYPE.STRSXP, values.Length);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);
            for (int i = 0; i < values.Length; ++i)
            {
                R.SET_STRING_ELT(val, i, R.Rf_mkChar(values[i]));
            }
            R.Rf_setVar(pname, val, R_Global_Env);
            R.Rf_unprotect(2);
            return this;
        }
        /// <summary>
        /// Assign a value to a symbol ahaped as a data frame.
        /// </summary>
        /// <param name="name">The name of the symbol to assign to</param>
        /// <param name="tuples">a collection of object[] containing the tuples representing the data frame rows.</param>
        /// <param name="colNames">The names to assign to each column of the dataframe, if omitted defaults are created.</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name,IEnumerable<object[]> tuples, string[] colNames)
        {
            return SetVar(name,tuples,colNames, -1);
        }
        /// <summary>
        /// Assign a value to a symbol ahaped as a data frame.
        /// </summary>
        /// <param name="name">The name of the symbol to assign to</param>
        /// <param name="tuples">a collection of object[] containing the tuples representing the data frame rows.</param>
        /// <param name="colNames">The names to assign to each column of the dataframe, if omitted defaults are created.</param>
        /// <param name="rowNameOrdinal">The index of the element on each row to be used as row name.</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name,IEnumerable<object[]> tuples, string[] colNames, int rowNameOrdinal)
        {
            int len = tuples.Count();
            if (null == colNames)
                colNames = new string[0];
            if (len == 0)
                throw new Exception("Cannot create an empty data frame");
            int fieldCount = tuples.First().Length - (rowNameOrdinal >= 0 ? 1 : 0);
            if( fieldCount<=0 )
                throw new Exception("Cannot create an data frame with empty rows");
            Type[] fieldTypes = tuples.First().Select(q => q.GetType()).ToArray();

            IntPtr col = R.Rf_allocVector(SEXPTYPE.VECSXP, fieldCount);
            R.Rf_protect(col);

            IntPtr rowNames=IntPtr.Zero;
            int vIndex = 0;
            for (int i = 0; i < tuples.First().Length; ++i)
            {
                if (i != rowNameOrdinal)
                {
                    IntPtr fieldVector = GetProtectedVectorForType(fieldTypes[i], len);
                    FillVector(fieldVector, tuples.Select(q => q[i]).ToArray(), fieldTypes[i]);
                    R.SET_VECTOR_ELT(col, vIndex++, fieldVector);
                    R.Rf_unprotect(1);
                }
                else
                {
                    
                    //rowNames = R.Rf_allocVector(SEXPTYPE.STRSXP, len);
                    //R.Rf_protect(rowNames);
                    //FillVector(rowNames, tuples.Select(q => q[i]).ToArray(), typeof(string));
                    rowNames = GetProtectedVectorForType(fieldTypes[rowNameOrdinal], len);
                    FillVector(rowNames, tuples.Select(q => q[i]).ToArray(), fieldTypes[i]);
                }
            }
            if (rowNameOrdinal < 0)
            {
                rowNames = R.Rf_allocVector(SEXPTYPE.INTSXP, len);
                R.Rf_protect(rowNames);
                FillVector(rowNames,CountArray(len),typeof(int));
            }

            //change class to data frame
            IntPtr cl = R.Rf_mkString("data.frame");
            R.Rf_protect(cl);
            R.Rf_setAttrib(col, R_ClassSymbol, cl);
            R.Rf_unprotect(1);

            //set row names
            R.Rf_setAttrib(col, R_RowNamesSymbol, rowNames);

            //set col names
            IntPtr colNamesVector = R.Rf_allocVector(SEXPTYPE.STRSXP,Math.Max(fieldCount,colNames.Length));
            R.Rf_protect(colNamesVector);
            for (int i = 0; i < colNames.Length; ++i)
            {
                IntPtr tmp = R.Rf_mkChar(colNames[i]);
                R.Rf_protect(tmp);
                R.SET_STRING_ELT(colNamesVector, i,tmp );
                R.Rf_unprotect(1);
            }
            for (int i = colNames.Length; i < fieldCount; ++i)
            {
                IntPtr tmp = R.Rf_mkChar(string.Format("NONAME"+(i+1).ToString()));
                R.Rf_protect(tmp);
                R.SET_STRING_ELT(colNamesVector, i, tmp);
                R.Rf_unprotect(1);
            }
            R.Rf_setAttrib(col, R_NamesSymbol, colNamesVector);
            R.Rf_unprotect(1);

            //inject the variable
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            R.Rf_setVar(pname, col, R_Global_Env);
            R.Rf_unprotect(3);
            return this;
        }

        private object[] CountArray(int len)
        {
            object[] res = new object[len];
            for (int i = 1; i <= len; ++i)
            {
                res[i - 1] = i;
            }
            return res;
        }

        private void FillVector(IntPtr fieldVector, object[] p, Type type)
        {
            IntPtr unmanagedArray;
            switch (type.Name)
            {
                case "DateTime":
                    unmanagedArray = R.REAL(fieldVector);
                    Marshal.Copy(MakeTypedArray<double>(p), 0, unmanagedArray, p.Length);
                    break;
                case "Double":
                case "Single":
                case "Decimal":
                    unmanagedArray = R.REAL(fieldVector);
                    Marshal.Copy(MakeTypedArray<double>(p), 0, unmanagedArray, p.Length);
                    break; 
                case "Int32":
                case "Int16":
                case "Int64":
                    unmanagedArray = R.INTEGER(fieldVector);
                    Marshal.Copy(MakeTypedArray<int>(p), 0, unmanagedArray, p.Length);
                    break; 
                case "Boolean":
                    unmanagedArray = R.INTEGER(fieldVector);
                    Marshal.Copy(MakeTypedArray<int>(p), 0, unmanagedArray, p.Length);
                    break; 
                default:
                    string[] strings = MakeTypedArray<string>(p);
                    for (int i = 0; i < strings.Length; ++i)
                    {
                        IntPtr tmp = R.Rf_mkChar(strings[i]);
                        R.Rf_protect(tmp);
                        R.SET_STRING_ELT(fieldVector, i,tmp );
                        R.Rf_unprotect(1);
                    }
                    break;
            }
            
        }

        private T[] MakeTypedArray<T>(object[] p)
        {
            T[] res = new T[p.Length];
            int i = 0;
            foreach (object o in p)
            {
                if (o == null)
                    res[i] = default(T);
                else
                {
                    if (o is DateTime)
                    {
                        res[i] =  (T)Convert.ChangeType(((DateTime)o - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds,typeof(T));
                    }
                    else
                    {
                        res[i] = (T)Convert.ChangeType(o, typeof(T));
                    }
                }
                ++i;
            }
            return res;
        }


        
        private IntPtr GetProtectedVectorForType(Type type, int len)
        {
            bool posixCT = false;
            SEXPTYPE vectorType = SEXPTYPE.NILSXP;
            switch (type.Name)
            {
                case "DateTime":
                    vectorType = SEXPTYPE.REALSXP;
                    posixCT = true;
                    break;
                case "Double":
                case "Single":
                case "Decimal":
                    vectorType = SEXPTYPE.REALSXP;
                    break;
                case "Int32":
                case "Int16":
                case "Int64":
                    vectorType = SEXPTYPE.INTSXP;
                    break;
                case "Boolean":
                    vectorType = SEXPTYPE.LGLSXP;
                    break;
                default:
                    vectorType = SEXPTYPE.STRSXP;
                    break;
            }
            IntPtr v = R.Rf_allocVector(vectorType, len);
            R.Rf_protect(v);
            if (posixCT)
            {
                //change class to time
                IntPtr cl = GetProtectedVectorForType(typeof(string), 2);
                FillVector(cl, new string[] { "POSIXt", "POSIXct" }, typeof(string));
                R.Rf_setAttrib(v, R_ClassSymbol, cl);

                IntPtr tzone = R.Rf_install("tzone");
                R.Rf_protect(tzone);
                R.Rf_setAttrib(v, tzone, R.Rf_mkString("GMT"));
                R.Rf_unprotect(2);
            }
            return v;
        }
        /// <summary>
        /// Assign a value to a symbol
        /// </summary>
        /// <param name="name">Symbol name</param>
        /// <param name="value">The value</param>
        /// <returns>The current engine instance ( for fluent interface purposes)</returns>
        public REngine SetVar(string name, int value)
        {
            IntPtr val = R.Rf_ScalarInteger(value);
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(val);
            R.Rf_protect(pname);
            R.Rf_setVar(pname, val, R_Global_Env);
            R.Rf_unprotect(2);
            return this;
        }
        private void OnBusy(uint param)
        {
            
        }
        private void OnCallback()
        {
            

        }
        private int OnWriteConsole(string p1)
        {
            if (null != WriteMessage)
            {
                WriteMessage(this,new MessageEventArgs(){Message=p1});
            }
            return 0;
        }
        private int OnYesNoCancel(string msg)
        {
            return 0;
        }
        private void OnShowMessage(string msg)
        {
            
        }
        private int OnReadConsole( string p1
                                  ,  IntPtr s1
                                   , UInt32 i1
                                   , UInt32 i2)
        {
            if (null != AskForInput)
            {
                AskUserEventArgs aue = new AskUserEventArgs() { Prompt=p1 };
                AskForInput(this, aue);
                byte[] ansi = UTF8Encoding.UTF8.GetBytes(aue.UserReply+"\n\0");
                Marshal.Copy(ansi, 0, s1, ansi.Length);
                return aue.UserReply.Length;
            }
            else
                return 0;
        }
        private IntPtr R_Global_EnvPtr;
        private IntPtr R_ClassSymbol;
        private IntPtr R_NamesSymbol;
        private IntPtr R_RowNamesSymbol;
        private IntPtr R_Global_Env;
        private IntPtr R_NilValue;
        internal IntPtr R_UnboundValue;
        /// <summary>
        /// Value to assign to have an integer NA ( not available)
        /// </summary>
        public int NaInteger { get; internal set; }
        /// <summary>
        /// Value to assign to have a real NA ( not available)
        /// </summary>
        public double NaReal { get; internal set; }

        /// <summary>
        /// Value to specify a NaN to R
        /// </summary>
        public double NaN { get; internal set; }

        /// <summary>
        /// Value to specify a positive infinite to R
        /// </summary>
        public double InfinitePositive { get; internal set; }

        /// <summary>
        /// Value to specify a negative infinite to R
        /// </summary>
        public double InfiniteNegative { get; internal set; }

        private REngine(bool interactive)
        {
            ProbeEnvironment();
            this.interactive = interactive;
            string s = R.getDLLVersion();
            R.R_DefParams( ref RStart);
            RStart.rhome = Marshal.StringToHGlobalAnsi(homeDirectory);
            RStart.home = Marshal.StringToHGlobalAnsi(homeUser);
            //RStart.R_Verbose = true;
            RStart.R_Interactive = true;
            RStart.R_Quiet = false;
            //RStart.R_Slave = true;
            RStart.SaveAction = StartupAction.SA_NOSAVE;
            RStart.RestoreAction = StartupAction.SA_RESTORE;
            RStart.Busy = new FunctionPointers.Blah6(OnBusy);
            RStart.CallBack = new FunctionPointers.Blah3(OnCallback);
            RStart.WriteConsoleEx = new FunctionPointers.Blah5(OnWriteConsole);
            RStart.WriteConsole = null;
            RStart.YesNoCancel = new FunctionPointers.Blah5(OnYesNoCancel);
            RStart.ShowMessage = new FunctionPointers.Blah4(OnShowMessage);
            RStart.ReadConsole = new FunctionPointers.Blah1(OnReadConsole);
            RStart.CharacterMode = UIMode.LinkDLL;

            keepHandles[0] = GCHandle.Alloc(RStart.Busy);
            keepHandles[1] = GCHandle.Alloc(RStart.CallBack);
            keepHandles[2] = GCHandle.Alloc(RStart.ReadConsole);
            keepHandles[3] = GCHandle.Alloc(RStart.ShowMessage);
            keepHandles[4] = GCHandle.Alloc(RStart.YesNoCancel);
            keepHandles[5] = GCHandle.Alloc(RStart.WriteConsoleEx);
            keepHandles[6] = GCHandle.Alloc(RStart);

            R.R_SetParams(ref RStart);
            R.R_SizeFromEnv(ref RStart);
            R.R_SetParams(ref RStart);
            R.R_set_command_line_arguments(0, new string[0]);
            //1)
            //R.setup_term_ui();
            //R.run_Rmainloop();
            //2)
            //R.setup_Rmainloop();
            //R.R_ReplDLLinit();
            IntPtr h = Winapi.LoadLibrary("R.dll");
            R_Global_EnvPtr = Winapi.GetProcAddress(h, "R_GlobalEnv");
            R.setup_term_ui();
            R.setup_Rmainloop();
            R_Global_Env = Marshal.ReadIntPtr(R_Global_EnvPtr);
            R_ClassSymbol = Marshal.ReadIntPtr(Winapi.GetProcAddress(h, "R_ClassSymbol"));
            R_RowNamesSymbol = Marshal.ReadIntPtr(Winapi.GetProcAddress(h, "R_RowNamesSymbol"));
            R_NamesSymbol = Marshal.ReadIntPtr(Winapi.GetProcAddress(h, "R_NamesSymbol"));
            R_UnboundValue = Marshal.ReadIntPtr(Winapi.GetProcAddress(h, "R_UnboundValue"));
            R_NilValue = Marshal.ReadIntPtr(Winapi.GetProcAddress(h, "R_NilValue"));
            NaInteger = Marshal.ReadInt32(Winapi.GetProcAddress(h, "R_NaInt"));
            NaReal = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Winapi.GetProcAddress(h, "R_NaReal")));
            NaN = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Winapi.GetProcAddress(h, "R_NaN")));
            InfinitePositive = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Winapi.GetProcAddress(h, "R_PosInf")));
            InfiniteNegative = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Winapi.GetProcAddress(h, "R_NegInf")));
        }

        /// <summary>
        /// Executes an R script
        /// </summary>
        /// <param name="code"></param>
        public void Execute(string code)
        {
            code = code.Replace("\r\n", "\n");
            IntPtr codeSexp = R.Rf_mkString(code);
            R.Rf_protect(codeSexp);
            RParseStatus status;
            IntPtr parsedCode = R.R_ParseVector(codeSexp, -1, out status, R_NilValue);
            R.Rf_protect(parsedCode);
            int error;
            int len = R.Rf_length(parsedCode);
            for (int i = 0; i < len; ++i)
            {
                IntPtr line = R.VECTOR_ELT(parsedCode, i);
                R.Rf_protect(line);

                IntPtr res = R.R_tryEval(line, R_Global_Env, out error);
                R.Rf_unprotect(1);
                R.Rf_protect(res);
                R.Rf_unprotect(1);
            }
            R.Rf_unprotect(2);
        }

        internal IntPtr GetUnboundValue()
        {
            return R_UnboundValue;
        }
        private void ProbeEnvironment()
        {
            homeDirectory = Environment.GetEnvironmentVariable("R_HOME");
            if (string.IsNullOrEmpty(homeDirectory))
            {
                UIntPtr key;
                if (Winapi.ERROR_SUCCESS == Winapi.RegOpenKeyEx(Winapi.HKEY_LOCAL_MACHINE, @"SOFTWARE\R-core\R", 0, Winapi.KEY_QUERY_VALUE, out key))
                {
                    uint t;
                    uint len=600;
                    StringBuilder data=new StringBuilder();
                    data.EnsureCapacity((int)len);
                    Winapi.RegQueryValueEx(key, "InstallPath", 0, out t, data, ref len);
                    homeDirectory = data.ToString();
                }
            }
            if( string.IsNullOrEmpty(homeDirectory) )
                throw new Exception("R home variable not set. R does not appear in the registry as well. Please ensure R is installed and properly configured.");
            homeUser = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            Environment.SetEnvironmentVariable("R_HOME", homeDirectory);
        }
        #region IDisposable Members
        public void Dispose()
        {
                      
        }
        #endregion

        public void Start()
        {
            
            if (interactive)
                R.run_Rmainloop();
        }

        internal object GetScalar(IntPtr cell)
        {
            throw new NotImplementedException();
        }
        internal DateTime FromUnix(double q)
        {
            return new DateTime(1970, 1, 1, 0, 0, 0) + TimeSpan.FromSeconds(q);
        }
        internal Array ToNetArray(IntPtr col)
        {
            int len = R.Rf_length(col);
            if (R.Rf_isReal(col))
            {
                double[] d = new double[len];
                Marshal.Copy(R.REAL(col), d, 0, len);
                if (IfDateTime(col))
                {
                    return d.Select(q => FromUnix(q)).ToArray();
                }
                else
                {
                    return d;
                }
            }
            else
            if (R.Rf_isLogical(col))
            {
                int[] d = new int[len];
                Marshal.Copy(R.INTEGER(col), d, 0, len);
                return d.Select(q => q!=0).ToArray();
            }
            else
            if (R.Rf_isInteger(col))
            {
                int[] d = new int[len];
                Marshal.Copy(R.INTEGER(col), d, 0, len);
                return d;
            }
            else
            if(R.Rf_isString(col) )
            {
                string[] d = new string[len];
                for(int i=0; i<len;++i )
                {
                    IntPtr s = R.STRING_ELT(col, i);
                    R.Rf_protect(s);
                    try
                    {
                        d[i] = R.R_CHAR(s);
                    }
                    catch (Exception)
                    {
                        unsafe
                        {
                            d[i] = Marshal.PtrToStringAnsi(s, R.Rf_length(s) + 8);
                        }
                    }
                    R.Rf_unprotect(1);
                }
                return d;
            }
            return null;
        }
        internal bool IfDateTime(IntPtr var)
        {
            string[] atts = GetClass(var);
            if (atts.Length != 2)
                return false;
            return atts[0] == "POSIXt" && atts[1] == "POSIXct";
        }
        internal Array GetRowNames(IntPtr obj)
        {
            Array res = null;
            IntPtr names = R.Rf_getAttrib(obj, R_RowNamesSymbol);
            R.Rf_protect(names);
            res = ToNetArray(names);
            R.Rf_unprotect(1);
            return res;
        }
        internal Array GetNames(IntPtr obj)
        {
            Array res = null;
            IntPtr names = R.Rf_getAttrib(obj, R_NamesSymbol);
            R.Rf_protect(names);
            res = ToNetArray(names);
            return res;
        }
    }
}
